﻿using System;
using System.ComponentModel;
using System.Linq;
using ALDIOrganizer.Properties;

namespace Generator
{
    /// <summary>
    /// performs stochastic / darwinian optimization by randomly mutating a large geneticPool of solutions [Solution]
    /// </summary>
    /// <remarks>
    /// 
    /// </remarks>
    public static class GeneticOptimizer
    {
        private static StochasticParameters _parameters;
        private static Solution[] _geneticPool;
        private static readonly Random random;
        private static int _shiftsCount;
        private static int _employeeCount;
        internal static Solution Initial;
        public static Solution Champion;
        
        private static int _temperature;
        private static BackgroundWorker worker;

        static GeneticOptimizer()
        {            
            random = new Random();            
        }

        public static void SetInitialSolution(Solution solution)
        {
            Initial = solution;
            _shiftsCount = Initial.Shifts.Length;
            _employeeCount = Initial.Candidates.Length;
        }

        public static void PassMeBackgroundWorker(BackgroundWorker w)
        {
            worker = w;
        }

        internal static int randomShift()
        {
            return random.Next(_shiftsCount);
        }

        internal static int randomEmployee()
        {
            return random.Next(_employeeCount);
        }

        #region testers - methods which do not modify anything, they only check if modifications are possible

        

        /* NOTE: methods swappable and movable - to some extent - use the same code.
         * I'm aware of the principle "don't repeat yourself",
         * but if I merged them into one universal method, it would look awful and not very readable. */
        
        internal static bool Swappable(Solution solution, int id1, int id2)
        {
            Shift s1 = solution.Shifts[id1];
            Shift s2 = solution.Shifts[id2];

            // if that'pShift the same _employee, Shifts are not swappable of course
            if (s1.Chosen == s2.Chosen) return false;
            // we've passed that point ^
            // so it'pShift not the same _employee...
            // let'pShift meet both guys then!
            Candidate c1 = solution.Candidates[s1.Chosen];
            Candidate c2 = solution.Candidates[s2.Chosen];
                        
            /* now check whether they're both AMONG the considered Candidates for eachother'pShift Shift
             * a store assistant cannot swap with a store manager (even if he'pShift available at that specific Time),
             * because he can't run the store. */
            if (!s1.Candidates.Contains(s2.Chosen)) return false;
            if (!s2.Candidates.Contains(s1.Chosen)) return false;

            byte howManyShiftsC1doesOnS2day = 0;
            byte howManyShiftsC2doesOnS1day = 0;
            if (c1.Days[s2.Day]) howManyShiftsC1doesOnS2day += 1;
            if (c1.Revisits[s2.Day]) howManyShiftsC1doesOnS2day += 1;
            if (c2.Days[s1.Day]) howManyShiftsC2doesOnS1day += 1;
            if (c2.Revisits[s1.Day]) howManyShiftsC2doesOnS1day += 1;

            bool isItTheSameDay = (s1.Day == s2.Day);
            if (isItTheSameDay)
            {
                howManyShiftsC1doesOnS2day -= 1;
                howManyShiftsC2doesOnS1day -= 1;
            }
            if (howManyShiftsC1doesOnS2day == 2) return false;
            if (howManyShiftsC2doesOnS1day == 2) return false;

            bool canC1doS2 = false;
            bool canC2doS1 = false;

            if (howManyShiftsC1doesOnS2day == 0) canC1doS2 = true;
            if (howManyShiftsC2doesOnS1day == 0) canC2doS1 = true;
            if (canC1doS2 && canC2doS1) return true;

            if (!canC1doS2)
                canC1doS2 = !EngineAddRemoveShifts.DoesWorkAnyOverlappingShift(solution, c1.ID, s2.ID, s1.ID);
            if (!canC1doS2)
                return false;

            if (!canC2doS1)
                canC2doS1 = !EngineAddRemoveShifts.DoesWorkAnyOverlappingShift(solution, c2.ID, s1.ID, s2.ID);
            return canC2doS1;
        }

        internal static bool movable(Solution solution, int shift, int newcandidate)
        {   
            Shift s = solution.Shifts[shift];
            if (s.Chosen == newcandidate) return false; // if that'pShift the same guy, obviously we can't do nothing.
                        
            // so it'pShift not the same guy. let'pShift meet both!
            Candidate newGuy = solution.Candidates[newcandidate];

            if (newGuy.Revisits[s.Day]) return false; // if he already has two Shifts, he won't take a third one.

            // if he is not among the Candidates for the Shift - he won't take it
            if (!s.Candidates.Contains(newcandidate)) return false;

            // - we passed it ^  - so, in theory, he *can* do this Shift.

            /* if he works one Shift already - On that Day, we need to find out if he can't take another
             * do they overlap? for example, if he works 8-14, he can also work 17-cl if there'pShift a need.
             * but 12-cl: no, because both Shifts overlap */
            if (newGuy.Days[s.Day])
            {
                var conflict = EngineAddRemoveShifts.DoesWorkAnyOverlappingShift(solution, newGuy.ID, s.ID, -1);
                if (conflict) return false;
            }
            
            return true; // he'pShift either off, or there was no conflict

        }

        #endregion



        internal static bool TrySwap(Solution solution)
        {
            if ((_employeeCount < 2)
                | (_shiftsCount < 2))
            {
                return false; // not likely, but...
            }            

            var firstShift = randomShift();
            var secondShift = randomShift();
            while (secondShift == firstShift)
            {
                secondShift = randomShift();
            }

            if (!Swappable(solution, firstShift, secondShift))
            {
                // 
                return false;
            }

            int firstEmployee = solution.Shifts[firstShift].Chosen;
            int secondEmployee = solution.Shifts[secondShift].Chosen;

            // 

            EngineAddRemoveShifts.TakeShiftOff(solution, firstShift, firstEmployee);
            EngineAddRemoveShifts.TakeShiftOff(solution, secondShift, secondEmployee);

            EngineAddRemoveShifts.putShiftOn(solution, firstShift, secondEmployee);
            EngineAddRemoveShifts.putShiftOn(solution, secondShift, firstEmployee);
            
            return true;

        }

        internal static bool TryReassign(Solution solution)
        {
            if ((_employeeCount < 2)
                | (_shiftsCount < 2))
            {
                return false; // not likely, but...
            }

            int shift = randomShift();
            int newGuy = randomEmployee();
            int oldGuy = solution.Shifts[shift].Chosen;
            while (newGuy == oldGuy)
            {
                newGuy = randomEmployee();
            }            
            if (!movable(solution, shift, newGuy))
            {
                return false;
            }
            EngineAddRemoveShifts.TakeShiftOff(solution, shift, oldGuy);
            EngineAddRemoveShifts.putShiftOn(solution, shift, newGuy);
            return true;
        }


        

        /// <summary>
        /// populates 
        /// </summary>
        /// <param Name="original">
        /// the original solution (built up by Engine) to be improved upon
        /// </param>
        public static void PopulateThePool(Solution original)
        {
            for (int i = 0; i < _parameters.poolSize; i++)
            {
                _geneticPool[i] = original.DeepCopy();                
            }            
        }

        public static void mutate(Solution solution)
        {
            for (int times = 0; times < _temperature; times++)
            {
                for (int swapTimes = 0; swapTimes < _parameters.swapAttemptsIn1Go; swapTimes++)
                {
                    TrySwap(solution);
                }
                for (int reassignTimes = 0; reassignTimes < _parameters.reassignAttemptsIn1Go; reassignTimes++)
                {
                    TryReassign(solution);
                }
            }
            
        }
        
        /// <summary>
        /// overwrites weaker solutions in a geneticPool with better ones
        /// </summary>
        /// <remarks>
        /// this method assumes that the geneticPool is already sorted (descendingly!)
        /// </remarks>
        public static void replaceTheWeakies()
        {
            for (var i = 1; i < _parameters.poolSize; i++)
            {
                _geneticPool[i] =
                    _geneticPool[_parameters.replaceTable[i]].DeepCopy();
            }
            
        }

        
        

        public static void startTheGame(SpeedMode pSpeedMode)
        {
            _parameters = new StochasticParameters(true,
                pSpeedMode);
            
            _geneticPool = new Solution[_parameters.poolSize];
            
            Report report;
            Initial.ComputePenalty(false, out report);
            

            int bestSoFar = Initial.penalty;
            PopulateThePool(Initial); // makes {poolSize} (eg. 100) clones of the Initial solution.

            _temperature = _parameters.initialTemperature;
                        
            // the first iteration, all are identical, so we mutate all but the first one (preserving original solution)
            for (int i = 1; i < _parameters.poolSize; i++)
            {
                mutate(_geneticPool[i]);
                _geneticPool[i].ComputePenalty(false, out report);
            }
            Array.Sort(_geneticPool);

            DateTime start = DateTime.Now;

            // following iterations!
            for (int i = 0; i < (_parameters.iterations+1); i++)
            {
                if (worker.CancellationPending) return;
                
                int percent = (i * 100) / _parameters.iterations;
                worker.ReportProgress(
                    percent,
                    new[] 
                    {
                        percent + "%",
                        Resources.GENERATOR_OptimizingSolution
                    } );

                if(i < _parameters.iterations)
                    replaceTheWeakies();
                // 
                // 

                // 
                // now we are only mutating half of solutions
                for (int j = _parameters.applyMutationFrom; j < _parameters.poolSize; j++)
                {
                    mutate(_geneticPool[j]);
                    _geneticPool[j].ComputePenalty(false, out report);
                }

                _temperature = _parameters.temperatureTable[(percent==100) ? 99 : percent];

                Array.Sort(_geneticPool);
                // binarySearchForFlawed();

                if (_geneticPool[0].penalty >= bestSoFar)
                {
                    continue;
                }
                                        
                bestSoFar = _geneticPool[0].penalty;

                // 
                
            }

            Champion = _geneticPool[0].DeepCopy();

            
        }




        






    }
}
